package com.cfw.prototype;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import javax.swing.tree.DefaultMutableTreeNode;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 대신 com.cfw.collection.TreeSearch를 사용할 수 있음
 * <br/><b>History</b><br/>
 * <pre>
 * 2014. 7. 29. 최초작성
 * </pre>
 * @author 박형일
 * @version 1.0
 */
@Deprecated
public class TreeSearch {
	private static final Logger logger = LoggerFactory.getLogger(TreeSearch.class); 
	
	/**
	 * TreeSearch를 이용한 트리 데이터 조회시 각 노드, 자식노드에 대하여 호출되는 콜백
	 */
	public static abstract class AbstractNavTargetCallback {
		/**
		 * 소스의 탐색 중에 생성할 전환 복제 타깃 객체
		 */
		private final Object data;
		
		public AbstractNavTargetCallback(Object data) { this.data =	data; }
		
		public Object getData() { return data; }
		
		/**
		 * 소스의 루트객체에 대한 처리가 구현될 함수, 소스의 탐색노드는 새로운 객체를 생성 반환해야 하나,
		 * 루트객체는 data 멤버에 값을 지정하는 프로세스만 구현 
		 * @param parent
		 * @param userObject
		 */
		abstract public void onroot(Object parent, Object userObject);
		
		/**
		 * 소스의 탐색 노드인 userObject의 parent에 대한 처리가 구현될 함수
		 * 루트노드일 경우에는 parent가 null임, 반환값은 parent에 child로 형태로 구현될 객체  
		 * @param parent
		 * @param userObject
		 * @return
		 */
		abstract public Object onnode(Object parent, Object userObject);
		
		/**
		 * source 노드 탐색 중 하위노드들이 발견되었을때 호출됨
		 * parent에 처리되어야 할 내용 구현 (예: parent에 'child'를 키로 Map생성)
		 * @param parent
		 */
		abstract public void onchild(Object parent);
	}
	
	/**
	 * TreeSearch를 이용한 트리 데이터 조회시 각 노드와 자식노드를 이동하기 위하여 호출되는 콜백
	 * @param <T>
	 */
	public static abstract class AbstractNavSourceCallback {
		/**
		 * 탐색 대상 객체
		 */
		private final Object data;
		
		public AbstractNavSourceCallback(Object data) { this.data = data; }
		
		public Object getData() { return data; }
		
		abstract public boolean isLeaf(Object node);
		abstract public Object getNextSibling(Object node);
		abstract public Object getUserObject(Object node);
		abstract public Object getFirstChild(Object node);
	}	
	
	abstract static public class AbstractSearchMatchCallback {
		abstract public boolean match(Object userObject);
	}
	
		public static void logging(final String event) {
			logger.debug(String.format("*** [%-36s]", event));
		}
	
		public static void logging(final String event, final String target, final Object... pars) {
			final StringBuffer buf = new StringBuffer();
			
			buf.append(String.format("*** [%-20s/%-15s]", event, target));
			
			if (pars.length > 0) {
				for (int i = 0; i < pars.length; i++) {
					if (pars[i] == null) {
						buf.append(" *** null");
					} else
					if (pars[i] instanceof MutableKeyHolder) {
						final MutableKeyHolder holder = (MutableKeyHolder)pars[i];
						
						if (holder.getObject() != null) {
							Map<String, String> map = (Map<String, String>)holder.getObject();
							buf.append(String.format(" *** [%-30s]", map.toString()));
						}
					} else
					if (pars[i] instanceof DefaultMutableTreeNode) {
						final DefaultMutableTreeNode node =	(DefaultMutableTreeNode)pars[i];
						
						if (node.getUserObject() != null) {
							Map<String, String> map = (Map<String, String>)node.getUserObject();
							buf.append(String.format(" *** [%-30s]", map.toString()));
						}
					} else {
						buf.append(String.format(" *** [%-30s]", pars[i].toString()));
					}
				}
			}
			logger.debug(buf.toString());
		}
	
		private static void logging(Object source, MutableKeyHolder target, Stack<Object> stackSource, Stack<MutableKeyHolder> stackTarget, final Map<MutableKeyHolder, MutableKeyHolder> parentMap) {
			logger.debug("**********************************************************************************************");
			logging("state", "target,source", target, source);
			if (stackSource != null) { for (Object object : stackSource) { logging("state", "stackSource", object); } }
			if (stackTarget != null) { for (MutableKeyHolder holder : stackTarget) { logging("state", "stackTarget", holder); } }
			if (parentMap != null) { for (MutableKeyHolder key : parentMap.keySet()) { logging("state", "parentMap",  key.getObject(), parentMap.get(key)); } }
			logger.debug("**********************************************************************************************");
		}
		
	private static final String MSG_ERR_CB_NULL = "Navigate call back can't be null";
	private static final String MSG_ERR_SRC_NULL = "Source data can't be null";
	private static final String MSG_ERR_POP_NULL = "Source object Stack.pop return null";
	
	public static void navigate(final AbstractNavSourceCallback srcCallback, final AbstractNavTargetCallback callback) {
		if (srcCallback == null || callback == null) { throw new IllegalArgumentException(MSG_ERR_CB_NULL); }
		if (srcCallback.getData() == null) { throw new IllegalArgumentException(MSG_ERR_SRC_NULL); }
		
		// 자식노드 탐색시 현재 부모노드를 스택에 push, 자식노드의 탐색 종료 후 pop
		final Stack<Object> stackSource = new Stack<Object>(); 
		// 소스의 자식노드 탐색에 따른 전환복제 타깃의 탐색시 사용될 스택, 소스 자식노드 탐색 종료 후 pop 수행시, 타깃 데이터에도 동일 조작 수행
		final Stack<MutableKeyHolder> stackTarget = new Stack<MutableKeyHolder>(); 
		// 전환복제 타깃 노드들의 부모노드 정보 저장
		final HashMap<MutableKeyHolder, MutableKeyHolder> parentMap = new HashMap<MutableKeyHolder, MutableKeyHolder>(); 
		
		Object source = srcCallback.getData(); // 탐색대상 객체, 탐색시 자식노드로 참조가 재할당된다.
		MutableKeyHolder target = new MutableKeyHolder(callback.getData()); // 타깃객체에 대한 탐색참조객체생성 (타깃의 루트)

		boolean doRoot = true;
		while (source != null) {
			logging("before.onnode", "target,source", target.getObject(), source);
			
			if (doRoot) { // target은 현재 위치에서 source 같이 루트
				callback.onroot(target.getObject(), srcCallback.getUserObject(source));
				doRoot = false; // 최초 1회만 수행
			} else { // target은 현재 위치에서 source 상대적으로 부모노드
				// newNode는 source 객체의 정보에 대응하는 객체
				Object newNode = callback.onnode(target.getObject(), srcCallback.getUserObject(source));
				
				// 새로운 객체는 onnode에서만 생성되므로, 여기에만 parentMap에 추가하는 부분 구현
				MutableKeyHolder parent = target;
				target = new MutableKeyHolder(newNode);
				parentMap.put(target, parent);
			}
			
			logging("after.onnode", "target", target);
			
			if (srcCallback.isLeaf(source)) {
				logging("branch.leaf", "source", source);
				
				source = srcCallback.getNextSibling(source);
				target = parentMap.get(target);
				
				logging("before.get-sibling..", "target,source", target, source);
				
				// 다음 Sibling 또는 모든 Sibling에 대하여 처리하였다면, 스택에서 상위노드를 pop
				// - Sibling이 없다면, 해당 level에서 수행할 내용 없으므로, pop으로 그 이전의 node에 대하여 처리
				while (source == null && stackSource.size() > 0) {
					source = stackSource.pop();
					target = stackTarget.pop();
					
					if (source == null) { throw new IllegalStateException(MSG_ERR_POP_NULL); }
					
					source = srcCallback.getNextSibling(source);
					target = parentMap.get(target);

					logging("loop.get-sibling..", "target,source", target, source);
				}
			} else {
				// 하위노드 탐색이전에 스택에 현재 노드 push
				stackSource.push(source);
				stackTarget.push(target);
				
				source = srcCallback.getFirstChild(source);	
				
				logging("branch.has-child", "target,source", target, source);
				
				// 타깃객체의 자식노드 존재여부가 확인되는 시점에서 onchild callback 호출
				callback.onchild(target.getObject());
			}
			
			logging(source, target, stackSource, stackTarget, parentMap);
		}
	}			
}

/**
 * 가변객체를 맵의 키로 사용하기 위한 어댑터객체
 * - hashcode가 변동되기 때문에 맵을 맵의 키로 사용할 수 없다. 그러므로 object 애트리뷰트에 비교될 객체를 지정하고,
 * 맵의 get 메서드의 리턴에 사용될 equals, hashCode 메서드를 Override 함      
 * @author hipark
 *
 */
final class MutableKeyHolder {
	private static final int HASH_CODE = 2147483647;
	private final Object object;
	
	public MutableKeyHolder(final Object object) { this.object = object; }
	
	public Object getObject() { return object; }
	
	@Override
	public boolean equals(Object object) {
		if (this == object) { return true; }
		if (object == null) { return false; }
		if (!(object instanceof MutableKeyHolder)) { return false; }
		
		MutableKeyHolder other = (MutableKeyHolder)object;
		if (getObject() != other.getObject()) { return false; }
		
		return true;		
	}
	
	@Override
	public int hashCode() { return HASH_CODE; }
}